Аудит и исправление расчётов плагина ton-trading-bot (#182)#183
Аудит и исправление расчётов плагина ton-trading-bot (#182)#183konard wants to merge 10 commits into
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: xlabtg#182
Корневая причина: при открытии сделки entry_price_usd был опциональным. Когда агент его не указывал, но цена выхода присутствовала, closeTradeJournalEntry сравнивал сырое количество TON (как будто это USD) со стоимостью в USD, давая бессмысленные +$7.39 / +73.89% на сделке TON/USDT с почти неизменной ценой. - closeTradeJournalEntry: P&L считается только из величин в ОДНОЙ единице. Ценовой режим требует обе цены (вход+выход): pnl = base * (exit - entry); иначе безопасный fallback на сырую разницу в одной единице. - simulate_trade/execute_swap: автозапись цены входа from_asset (TON через getPrice, стейблкоины = $1), чтобы закрытие всегда было unit-safe. - record_trade: авто-вывод цены выхода (как в close_position). - auto_execute: цена входа берётся для from_asset, а не всегда TON. - Тесты: воспроизведение сценария xlabtg#182 (open без entry_price -> close) теперь даёт ~$0.58 / 3.45% вместо $7.39 / 73.89%.
Расхождения из отчёта: total_closed_trades=177 при win+loss=172 и "65 убытков, но средний убыток $0". Корень — сделки с pnl==0 (breakeven) и pnl==null (P&L не записан) молча приравнивались к 0: не попадали ни в win, ни в loss, но раздували знаменатель win_rate и средние значения. - Добавлен общий классификатор summarizeClosedPnl: каждая закрытая сделка попадает ровно в одну корзину, инвариант win+loss+breakeven+unscored=total. - get_portfolio_summary и get_performance_dashboard: новые поля breakeven_count/unscored_count; win_rate считается по решающим сделкам (win+loss); средние и realized P&L — только по сделкам с конечным pnl. - calculate_risk_metrics: несписанные сделки (null pnl_percent) исключены из ряда наблюдений (не искажают VaR/просадку/Sharpe), добавлено scored_trades и win/loss/breakeven счётчики. - Тесты: воспроизведение расхождения 177/172 и проверка, что несписанные сделки не разбавляют win_rate и не дают ложный средний убыток $0.
…х сделок Корень проблемы (issue xlabtg#182): ни один инструмент не переводил запланированную сделку из статуса 'pending' в 'executed'. Из-за этого исполненный due-ордер навсегда оставался 'pending', снова попадал в выборку due и мог быть исполнен повторно — двойная трата реальных средств. Изменения: - Новый инструмент ton_trading_execute_scheduled_trade (dm-only): атомарно захватывает ордер через compare-and-swap (UPDATE ... WHERE status='pending'), затем выполняет своп. Повторный вызов по тому же id — no-op. - Семантика отказа, безопасная для реальных средств: исключение реального свопа после захвата → терминальный статус 'failed' (состояние в сети неизвестно, без авто-ретрая); исключение симуляции или провал валидации до свопа → возврат в 'pending' (безопасный ретрай). - Общие хелперы recordSimulatedSwap/recordRealSwap — единый источник логики свопа и журналирования для simulate_trade, execute_swap и нового инструмента. - Миграция: колонка trade_id в scheduled_trades связывает ордер с журналом. - Статус-схема scheduled_trades дополнена 'failed'. - Описания schedule_trade/get_scheduled_trades/create_schedule направляют LLM к последовательности get → execute (вместо прямого swap/simulate). - Версия 2.2.0 → 2.3.0 (index.js + manifest.json), запись инструмента в манифест. - 7 новых тестов, включая защиту от двойного исполнения и парковку failed-ордера.
Аудит выявил несколько ошибок в расчётах, искажавших сигналы и метрики: - OHLCV от GeckoTerminal приходит newest-first (по убыванию timestamp). Код брал closes[last] как текущую цену, получая самую СТАРУЮ свечу, а RSI/MACD считались по перевёрнутому времени. Добавлена сортировка по возрастанию timestamp в get_technical_indicators. - Сигнальная линия MACD считалась как 9-периодная EMA сырых цен, а не самой линии MACD. Введён emaSeries(): signal = 9-EMA ряда MACD (его определение), гистограмма теперь корректна. - Sharpe ratio использовал популяционную дисперсию (делитель n). Заменено на выборочное стандартное отклонение (n-1) в calculate_risk_metrics и backtest; добавлен guard на >= 2 наблюдения, чтобы не делить на ноль. - VaR применял Math.abs() к процентилю доходности, превращая выигрышный персентиль в фантомный «риск». Теперь value_at_risk = max(0, -var95): ноль, когда исторических убытков на этом уровне доверия нет. - Исправлен неверный комментарий формулы Kelly (код был верным). Тесты: порядок свечей (current_price = новейшая), сигнальная линия MACD, Sharpe (n-1) в risk-metrics и backtest, нулевой VaR для прибыльной истории.
issue xlabtg#182: closeOpenPosition и closeTradeJournalEntry не были защищены от гонок и повторов, из-за чего одну позицию можно было закрыть дважды — дважды отправить обратный своп (реальные средства) или дважды начислить sim-баланс. - closeOpenPosition: атомарный захват 'open' → 'closing' перед любым движением средств; победитель CAS продолжает, остальные получают отказ. Симуляция при ошибке/пустом выводе освобождает захват ('closing' → 'open') для безопасного повтора; реальный своп при исключении или неизвестном выводе переводит сделку в терминальный 'close_failed' (ручная сверка), чтобы авто-повтор не потратил средства повторно. - closeTradeJournalEntry: закрытие через CAS (WHERE status != 'closed'), чтобы два конкурентных record_trade/close не начислили sim-баланс дважды. - Схема trade_journal.status: добавлены 'closing' и 'close_failed'. - Тесты: воспроизведение гонки реального закрытия (своп ровно один раз) и гонки record_trade (sim-баланс начисляется один раз); мок makeStatefulDb честно моделирует CAS-переходы trade_journal; инлайн-моки возвращают changes. Тесты: 180/180. Lint/typecheck/validate чисто.
Реальный режим auto_execute больше не обходит проверки для не-TON активов и не дублирует своп/INSERT в обход общих хелперов. - Валидация была привязана к from_asset === "TON": реальные продажи джеттонов пропускали проверку кошелька, баланса и maxTradePercent, а своп вызывался напрямую. Теперь путь идёт через recordRealSwap / recordSimulatedSwap — те же гарантии, что у execute_swap и execute_scheduled_trade (проверка инициализации кошелька, безопасный вывод entry price, контракт «бросать исключение при сбое после свопа»). - Риск-лимит (%% от баланса и минимальный резерв) унифицирован: считается в TON, поэтому применяется к продажам TON в обоих режимах; в реальном режиме добавлена недостающая проверка резерва после сделки. - recordRealSwap: если своп прошёл on-chain, но запись в журнал упала, пишется громкий CRITICAL-лог (средства ушли без записи — нужна ручная сверка) и исключение пробрасывается дальше. Тесты (воспроизводят баги, падают на старом коде): - реальная не-TON продажа без кошелька больше не вызывает swap; - happy-path реальной не-TON продажи проходит через recordRealSwap; - сбой записи журнала после реального свопа логируется как CRITICAL.
portfolio_summary теперь реально считает нереализованный P&L, который обещало описание: по каждой открытой позиции стоимость и база считаются в одной единице (USD) через цены TON/стейблкоинов, позиции без известной цены помечаются как unvalued, а не смешиваются. total_exposure_ton больше не суммирует только TON-фундированные ноги — экспозиция остальных мостится через USD-базу и цену TON, с флагом exposure_complete честности покрытия. get_top_traders: убрана выдуманная win_rate, которая вычиталась из цен двух РАЗНЫХ токенов пула (класс бага xlabtg#182) и фильтровала активные кошельки. Теперь ранжирование по объёму в USD и количеству сделок (одна единица), с note, что прибыльность по снимку сделок не определить. get_trader_performance: вместо сравнения amount_in/amount_out двух разных джеттонов (1 TON → 5 000 000 мемкоина всегда «выигрыш») считается поток TON в единых нанотонах: ton_spent/ton_received/net_ton_flow и buys/sells. Тесты-репродукции для всех трёх инструментов, каждый проверен на падение на старом коде. Итого 186/186.
optimal_position_size: ограничить fixed-fraction размер балансом — спотовая торговля на TON без плеча, поэтому позиция не может превышать кошелёк (risk 2% / stop 1% давало 200% баланса). Добавлен флаг fixed_fraction_capped_by_balance. order_book_depth: переименовать вводящее в заблуждение поле bid_ask_spread в price_spread_across_sizes — котировки односторонние (from→to), поэтому это не настоящий bid/ask-спред, а разброс цены по размерам заявки. Описание уточнено. README: добавлен пропущенный инструмент execute_scheduled_trade в список (42/42), в таблицу и в перечень dm-only; убрано устаревшее «ranked by win rate» у get_top_traders; описание order_book_depth приведено в соответствие. manifest: версия 2.3.0 → 2.4.0, описание order_book_depth уточнено. Каждое исправление сопровождается воспроизводящим тестом, который падает на старом коде. Полный прогон: 188/188 для ton-trading-bot.
Working session summaryВсё завершено. Итоговый отчёт по issue #182. Что сделаноКорневая причина почти всех багов — смешивание единиц измерения (TON, USDT, USD, разные джеттоны вычитались/складывались напрямую). Исправлено по всем пунктам issue плюс полный аудит. По пунктам issue:
Аудит сверх issue: порядок свечей OHLCV, математика RSI/MACD/Bollinger/VaR/Sharpe, защита закрытия позиций (CAS), безопасный путь Верификация
PR#183 готов к ревью (снят статус draft): #183 Заголовок и описание обновлены на русском, с разбивкой по каждому пункту issue. Все коммиты в ветке
This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:Claude Opus 4.8: (10 sub-sessions)
Total: (108.2K new + 1.3M cache writes + 35.0M cache reads) input tokens, 568.8K output tokens, $40.617133 cost Claude Haiku 4.5:
Total: 14.4K input tokens, 674 output tokens, $0.027739 cost 🤖 Models used:
📎 Log file uploaded as Repository (32435KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
This reverts commit 6e63549.
Что и почему
Issue #182 сообщает о критических ошибках в расчётах плагина
ton-trading-botи просит провести полный аудит и привести плагин в состояние, готовое к реальным транзакциям. Корневая причина почти всех багов — смешивание единиц измерения (TON, USDT, USD, разные джеттоны складывались/вычитались напрямую).Каждое исправление сопровождается воспроизводящим тестом, который падает на старом коде и проходит на новом.
Соответствие пунктам issue
🚨 1. Критический баг: смешение единиц в P&L
Было:
pnl = exit_quote_amount − entry_base_amount(вычитали 10 TON из 17.4 USDT → фейковые +$7.39 / +73.89 % на плоском трейде).Стало (
closeTradeJournalEntry,index.js): P&L считается из двух величин в одной единице:pnl = base_amount × (exit_price − entry_price);amount_out − amount_in), только когда обе ноги в одной единице.Дополнительно исправлено начисление на симуляционный баланс: раньше на TON-баланс зачислялась сумма выхода в USDT (отсюда «доказательство из баланса» 1000 → 1004.25 в отчёте). Теперь баланс остаётся в TON:
amount_in + pnl / exit_ton_price_usd. Цена входа/выхода резолвится автоматически (inferAssetPriceUsd), даже если агент её не передал, — это устраняет первопричину.📊 3. Статистические нестыковки
total_closed_trades≠win + loss, и «65 поражений при average loss $0». Введён единый классификатор закрытых сделок (classifyClosedTrades): сделка считается выигрышной/проигрышной/безубыточной строго по знаку реализованногоpnl, а сделки без посчитанного P&L помечаются какunscored. Теперь во всех инструментах статистики выполняется тождествоtotal_closed = wins + losses + breakeven + unscored.⏱ 4. Зависание DCA-ордеров (0 из 30 исполнено)
Корневая причина — отсутствие атомарного исполнения: ордер не «забирался» перед свопом. Добавлен
ton_trading_execute_scheduled_trade— он помечает due-ордерexecutedчерез compare-and-swap (CAS) до свопа, поэтому ордер не может исполниться дважды и не зависает вpending. Инструментdm-only.📉 5. Проскальзывание при редком мониторинге
По природе планировщик опрашивает цену периодически, поэтому выход возможен не точно на уровне TP. Закрытие теперь фиксирует фактическую цену выхода в
exit_price_usd, а реализованный P&L считается от неё — отчёт перестаёт выдавать желаемое за действительное.Полный аудит (сверх пунктов issue)
close_position/close_all_positions) защищено CAS от двойного исполнения.auto_executeпереведён на единый безопасный путь реального свопа (recordRealSwap: проверка инициализации кошелька, журналирование после ончейна, CRITICAL-лог при сбое журнала).scope: "dm-only"(execute_swap,auto_execute,execute_scheduled_trade,close_position,close_all_positions).portfolio_summary: понозиционный нереализованный P&L в USD и полная экспозиция в TON (не-TON ноги мостятся через USD); поляexposure_complete,valued/unvalued_open_positions.get_top_traders: убран бессмысленный «win rate» из сравнения цен двух разных токенов; ранжирование по наблюдаемому объёму в USD и числу сделок.get_trader_performance: вместо сравнения двух разных джеттонов — учёт потока TON (net_ton_flow) с честной пометкой, что это не реализованная прибыль.optimal_position_size: fixed-fraction размер ограничен балансом (спот без плеча не может превышать кошелёк); флагfixed_fraction_capped_by_balance.order_book_depth: полеbid_ask_spreadпереименовано вprice_spread_across_sizes— котировки односторонние, это не настоящий bid/ask-спред.execute_scheduled_trade), убраны устаревшие формулировки («ranked by win rate»), описания приведены в соответствие с кодом. Версия вmanifest.json: 2.2.0 → 2.4.0.Тестирование
eslint— 0 ошибок (остаются только унаследованные предупреждения о неиспользуемых переменных);tsc --noEmit— без ошибок;validate-plugins—ton-trading-bot: 42 tool(s) validated.Closes #182